feat(parquet): wire in additional parquet writer settings - #2887
feat(parquet): wire in additional parquet writer settings#2887xanderbailey wants to merge 3 commits into
Conversation
abcfba9 to
ca7d5ba
Compare
| /// writer is built, not when properties are parsed. | ||
| pub const PROPERTY_PARQUET_COMPRESSION_CODEC: &str = "write.parquet.compression-codec"; | ||
| /// Default Parquet compression codec (matches Iceberg's default since 1.4.0). | ||
| pub const PROPERTY_PARQUET_COMPRESSION_CODEC_DEFAULT: &str = "zstd"; |
There was a problem hiding this comment.
Have called this out in the PR description but current default is actually uncompressed from what I can see.
This opens up a question generally as to if we should copy the Java defaults for these settings which might be more intuitive for user or keep the defaults as the arrow-rs defaults... I don't have a strong opinion here so interested to hear people's thoughts.
There was a problem hiding this comment.
On the matter of inconsistency, there are other places where arrow-rs has different defaults to parquet-java, like the compression level for zstd too:
There was a problem hiding this comment.
I'm aligned with Matt's "Principle of Least Astonishment" and with the outcome from the Rust sync - let's adopt Java defaults unless we have a reason to diverge.
| assert_eq!(upper_bounds, HashMap::from([(0, Datum::int(i32::MAX))])); | ||
| } | ||
|
|
||
| // ----------------------------------------------------------------- |
There was a problem hiding this comment.
This looked very claude so I removed
|
Think I'll bring this one up as a topic for the rust community sync. It's not actually clear to me what our defaults should be here and if we should change them. I can see arguments both ways. |
|
My suggestion is to follow the Principle of Least Astonishment. Users interacting with their data lake across different Iceberg implementations might be surprised to get wildly different file size results from the de facto reference implementation (Java). I think in this case adhering to Java defaults, since it's likely something I don't expect to be codified in the spec, is reasonable. |
|
I'm inclined to agree with this take @mbutrovich |
| "lz4" => Compression::LZ4, | ||
| "lz4_raw" => Compression::LZ4_RAW, | ||
| "gzip" => { | ||
| let level = match level { |
There was a problem hiding this comment.
Can we please have these compression levels default to the same values as Java if a user doesn't pass us a compression level. Please reference this comment for examples - https://github.com/apache/iceberg-rust/pull/2887/changes#r3646169572
|
Overall looks great, thank you for finding and fixing this! I wonder if we could you use the comet testing proposal to validate the physical parquet matches between Java and Rust. |
| const DEFAULT_ZSTD_COMPRESSION_LEVEL: i32 = 3; | ||
| /// Default gzip level. | ||
| const DEFAULT_GZIP_COMPRESSION_LEVEL: u32 = 6; | ||
| /// Default brotli level. | ||
| const DEFAULT_BROTLI_COMPRESSION_LEVEL: u32 = 1; |
There was a problem hiding this comment.
Note that I'm not using the compression codec struct here because ti doesn't have all parquet compression types enumerated. That struct is used for metadata / avro compression levels etc rather than data files and I didn't want to leak parquet into the core crate.
There was a problem hiding this comment.
I think we should use the types in the compression module, unless we specifically want to diverge. While they're used primarily for metadata and puffin right now, I think they were written with data files in mind.
I'd extend that module to support brotli. It'll give us a common module where the conversions and errors are tested.
bc85d99 to
bc86d9f
Compare
bc86d9f to
da6cb46
Compare
Kurtiscwright
left a comment
There was a problem hiding this comment.
Looks just about ready to merge. Left 2 new comments, one is a request to santa claus and the other is a code style nit-pick so nothing that I would argue should block a merge.
| Compression::BROTLI(level) | ||
| } | ||
| "zstd" => { | ||
| let level = level.unwrap_or(DEFAULT_ZSTD_COMPRESSION_LEVEL); |
There was a problem hiding this comment.
Is there a particular reason to move away from the match level {} pattern and the level_as_u32?
There was a problem hiding this comment.
It’s actually just an parquet-rs api limitation, zstd accepts i32, the others require u32
| use crate::writer::{CurrentFileStatus, DataFile}; | ||
| use crate::{Error, ErrorKind, Result}; | ||
|
|
||
| /// Default compression levels, pinned to parquet-java's defaults so files are |
There was a problem hiding this comment.
I wish there was an integration test that would start failing if parquet-java ever changed their defaults.
There was a problem hiding this comment.
I don’t think we have any Java tests in the repo so I’m not sure the lift of adding those is worth it for this PR, what do you think?
There was a problem hiding this comment.
I don't think there's anything for this PR.
That being said, the proposed shared testing library might be interesting. I wonder if that can capture "this is what a Iceberg data file in Parquet looks like with table properties [X, Y, Z]". Then we can assert encodings, etc..
I think its just something to keep in mind for the future, at this point.
dannycjones
left a comment
There was a problem hiding this comment.
Thanks @xanderbailey, this has been on my TODO list, great to see these wired up!
My primary concern at the moment is around adding behavior beyond Iceberg's Java implementation.
| /// Parquet compression codec name (e.g. `zstd`, `gzip`). Validated when the | ||
| /// writer is built, not when properties are parsed. | ||
| pub parquet_compression_codec: String, |
There was a problem hiding this comment.
Validated when the writer is built, not when properties are parsed.
This is a good tenet for the whole module - i.e. we push validation to access, since we may not care if we're writing ORC files (as an example).
If you end up pushing another commit, I suggest we add it to the module comment.
| /// writer is built, not when properties are parsed. | ||
| pub const PROPERTY_PARQUET_COMPRESSION_CODEC: &str = "write.parquet.compression-codec"; | ||
| /// Default Parquet compression codec (matches Iceberg's default since 1.4.0). | ||
| pub const PROPERTY_PARQUET_COMPRESSION_CODEC_DEFAULT: &str = "zstd"; |
There was a problem hiding this comment.
I'm aligned with Matt's "Principle of Least Astonishment" and with the outcome from the Rust sync - let's adopt Java defaults unless we have a reason to diverge.
| use crate::writer::{CurrentFileStatus, DataFile}; | ||
| use crate::{Error, ErrorKind, Result}; | ||
|
|
||
| /// Default compression levels, pinned to parquet-java's defaults so files are |
There was a problem hiding this comment.
I don't think there's anything for this PR.
That being said, the proposed shared testing library might be interesting. I wonder if that can capture "this is what a Iceberg data file in Parquet looks like with table properties [X, Y, Z]". Then we can assert encodings, etc..
I think its just something to keep in mind for the future, at this point.
| /// Default compression levels, pinned to parquet-java's defaults so files are | ||
| /// comparable across implementations rather than tracking the parquet-rs | ||
| /// defaults, which may differ and could change without us noticing. |
There was a problem hiding this comment.
should this be a standard comment or module comment? this is currently attached to DEFAULT_ZSTD_COMPRESSION_LEVEL.
| /// | ||
| /// Currently translates the content-defined-chunking keys | ||
| /// (`write.parquet.content-defined-chunking.*`); other keys fall back to | ||
| /// parquet-rs defaults. |
| const DEFAULT_ZSTD_COMPRESSION_LEVEL: i32 = 3; | ||
| /// Default gzip level. | ||
| const DEFAULT_GZIP_COMPRESSION_LEVEL: u32 = 6; | ||
| /// Default brotli level. | ||
| const DEFAULT_BROTLI_COMPRESSION_LEVEL: u32 = 1; |
There was a problem hiding this comment.
I think we should use the types in the compression module, unless we specifically want to diverge. While they're used primarily for metadata and puffin right now, I think they were written with data files in mind.
I'd extend that module to support brotli. It'll give us a common module where the conversions and errors are tested.
| "snappy" => Compression::SNAPPY, | ||
| "lzo" => Compression::LZO, | ||
| "lz4" => Compression::LZ4, | ||
| "lz4_raw" => Compression::LZ4_RAW, |
There was a problem hiding this comment.
Where does this come from? I don't see it in the iceberg-java repo, and it's not documented for iceberg-java: https://iceberg.apache.org/docs/latest/configuration/#write-properties
I'd rather we don't go beyond Java without intent.
There was a problem hiding this comment.
I don’t think this is out of spec however, I would claim if a user wants to compress with a format supported by the parquet spec, this should be allowed?
There was a problem hiding this comment.
It's not about the spec, since I don't think the writer properties are considered part of the spec broadly.
I'm just a bit defensive of adding logic beyond what Java supports, since it is broadening the feature surface we're responsible for. Especially with Parquet, where some of the changes introduced are not forwards compatible.
I'm not strongly against adding these, happy to hear thoughts from others on this.
|
With this merged, I don't think we should close #2659 without a proper review into all configurations offered by the Java implementation. The scope is more than just Parquet writer properties. Happy to scope that down, but I'd like to have an issue somewhere to track the overall compatibility with table properties respected by Java. |
Which issue does this PR close?
What changes are included in this PR?
ParquetWriterBuilder::from_table_propertiespreviously only translated the content-defined-chunking settings; every other Parquet knob silently fell through to parquet-rs (arrow-rs) defaults, which diverge from the Java implementation. This PR maps the row-group / page / dictionary sizing and compression table properties into the parquet-rsWriterProperties:write.parquet.compression-codec(defaultzstd) andwrite.parquet.compression-levelwrite.parquet.row-group-size-bytes(default 128 MB)write.parquet.page-size-bytes(default 1 MB)write.parquet.page-row-limit(default 20000)write.parquet.dict-size-bytes(default 2 MB)How the new Iceberg defaults differ from the parquet-rs defaults that were previously in effect:
Are these changes tested?
Yes